package com.batdream.message.netty;

import com.batdream.message.enums.MsgActionEnum;
import com.batdream.message.pojo.ChatMsgByNetty;
import com.batdream.message.pojo.DataContent;
import com.batdream.message.service.ChatService;
import com.batdream.message.service.UserService;
import com.batdream.message.utils.JsonUtils;
import com.batdream.message.utils.SpringUtil;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Level;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author Wang Chen Peng
 * @date 2018/12/24
 * describe: 处理消息的handler
 * 在netty 中 TextWebSocketFrame 是用于为websocket专门处理文本的对象 frame是消息的载体
 * @since 1.8
 */
@Component
@Log4j2
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    private static UserService userService;
    private static ChatService chatService;
    private static UserChannelContext userChannelContext;
    private ChannelUserContext channelUserContext;

    /**
     * 用户记录和管理所有客户端的channel
     */
    private static ChannelGroup users = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    /**
     * 初始化依赖
     */
    public void init() {
        userService = (UserService) SpringUtil.getApplicationContext().getBean("userServiceImpl");
        userChannelContext = (UserChannelContext) SpringUtil.getApplicationContext().getBean("userChannelContext");
        chatService = (ChatService) SpringUtil.getApplicationContext().getBean("chatServiceImpl");
        channelUserContext = (ChannelUserContext) SpringUtil.getApplicationContext().getBean("channelUserContext");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
        //客户端传送过来的消息
        String content = textWebSocketFrame.text();
        //获取channel
        Channel channel = channelHandlerContext.channel();
        //转换接受到的内容
        DataContent dataContent;
        try {
            if (StringUtils.isNotBlank(content)) {
                dataContent = JsonUtils.jsonToPojo(content, DataContent.class);
            } else {
                //todo 返回既定格式数据
                log.debug("服务器接收到空参不做响应");
                return;
            }
        } catch (Exception e) {
            System.out.println("发生异常");
            //todo 返回既定格式数据
            log.error("客户端传入消息转换异常 消息内容为 : {}", content);
            e.printStackTrace();
            return;
        }

        //消息类型 根据不同的类型来处理不同的业务
        Integer action = dataContent.getAction();

        //第一次(或重连)初始化连接
        if (action != null && action.equals(MsgActionEnum.CONNECT.getType())) {
            //进行userId和channelId进行关联
            ChatMsgByNetty chatMsg = dataContent.getChatMsg();
            String sendUserId = chatMsg.getSenderId();
            //放入上下文容器之中
            if (userChannelContext.containsKey(sendUserId)) {

                log.error("异常注册 容器内已有链接", content);
                Channel oldChannel = userChannelContext.getChannel(sendUserId);
                channelUserContext.remove(oldChannel.id().asLongText());
                userChannelContext.remove(sendUserId);
                oldChannel.close();
                users.remove(oldChannel);

            }
            userChannelContext.addChannel(sendUserId, channel);
            channelUserContext.addUserId(channel.id().asLongText(), sendUserId);
            //todo 测试

            if (log.getLevel().intLevel() == Level.DEBUG.intLevel()) {
                System.out.println("==================测试容器内数据=====================");
                System.out.println("==================users=====================");
                users.forEach(value -> {
                    System.out.println(" ChannelGroup:id : " + value.id().asLongText());
                });
                System.out.println("==================userChannelContext=====================");
                userChannelContext.outPut();
                System.out.println("==================channelUserContext=====================");
                channelUserContext.outPut();
                System.out.println("==================测试容器内数据=====================");
            }

        }
        //聊天消息
        else if (action != null && action.equals(MsgActionEnum.CHAT.getType())) {
            //获取聊天内容
            ChatMsgByNetty chatMsgByNetty = dataContent.getChatMsg();
            if (Objects.nonNull(chatMsgByNetty)) {
                String receiverId = chatMsgByNetty.getReceiverId();
                String senderId = chatMsgByNetty.getSenderId();
                String msg = chatMsgByNetty.getMsg();
                //保存消息到数据库,并且标记为未签收
                String msgId = chatService.saveMsg(chatMsgByNetty);
                //设置id
                chatMsgByNetty.setMsgId(msgId);
                //发送消息 根据接收人的id去获取channel 并发送
                Channel receiverChannel = userChannelContext.getChannel(receiverId);
                //判断
                if (Objects.nonNull(receiverChannel)) {
                    //发送
                    Channel receiverUserChannel = users.find(receiverChannel.id());
                    if (Objects.nonNull(receiverUserChannel)) {
                        dataContent.setChatMsg(chatMsgByNetty);
                        //真正的发送
                        receiverUserChannel.writeAndFlush(new TextWebSocketFrame(JsonUtils.objectToJson(dataContent)));
                    } else {
                        //todo 客户端未在线 使用未在线处理方式
                        System.out.println(receiverChannel.id() + "客户端未在线");
                    }
                } else {
                    //todo 客户端未在线 使用未在线处理方式
                    System.out.println(receiverId + "客户端未在线");
                }
            } else {

            }
        }
        //消息签收
        else if (action != null && action.equals(MsgActionEnum.SIGNED.getType())) {
            //扩展字段再signed类型的消息中 代表需要去签收的消息id 逗号间隔
            String ids = dataContent.getExtand().trim();
            if (StringUtils.isNotBlank(ids)) {
                String[] idsArray = ids.split(",");
                List<String> idsList = Arrays.asList(idsArray).stream().filter(StringUtils::isNotBlank).collect(Collectors.toList());
                if (Objects.nonNull(idsList) && idsList.size() > 0) {
                    log.debug("接收到待签收的id列表中 有效id : {}", idsList.toString());
                    //进行签收
                    chatService.updateMsgToSigend(idsList);

                } else {
                    //todo 返回既定格式数据
                    //签收不能为空
                    log.error("错误的消息 ids为空");
                }
            } else {
                //todo 返回既定格式数据
                //签收不能为空
                log.error("错误的消息 ids为空");
            }
        }
        //客户端保持心跳
        else if (action != null && action.equals(MsgActionEnum.KEEPALIVE.getType())) {
            log.debug("收到客户端 id : {} 心跳消息", channel.id().asShortText());
            String keepLiveStr = "ping";
            String keepLiveResultStr = "pong";
            if (dataContent.getExtand().equals(keepLiveStr)){
                log.debug("收到客户端 id : {} 心跳消息  结果 : {}", channel.id().asShortText(),"符合规范");
                DataContent keepLiveResult = new DataContent();
                keepLiveResult.setAction(MsgActionEnum.KEEPALIVE.getType())
                        .setChatMsg(null)
                        .setExtand(keepLiveResultStr);
                //刷回客户端
                channel.writeAndFlush(new TextWebSocketFrame(JsonUtils.objectToJson(keepLiveResult)));
            }else{
                log.debug("收到客户端 id : {} 心跳消息  结果 : {}", channel.id().asShortText(),"不符合规范");
                //不符合规范则非正常请求 则关闭channel
                channel.close();
            }
        }
        //拉取好友
        else if (action != null && action.equals(MsgActionEnum.PULL_FRIEND.getType())) {

        }
        //不符合规范
        else {

        }


        //当websocket第一次open的时候 初始化channelGroup,channel和user关联

        //把聊天记录保存在数据库 标记聊天记录的签收状态

        //签收消息类型 针对消息进行签收 修改数据库中对应的签收状态

        //心跳类型的消息
    }

    /**
     * 当客户端连接到服务器之后
     * 获取到客户端的channel 并且放入到channelGroup中去进行管理
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //
        Channel channel = ctx.channel();
        users.add(channel);
    }

    /**
     * 关闭后触发的方法
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        //当触发handlerRemoved  ChannelGroup会自动移除对应客户端的channel
        //users.remove(ctx.channel());

            String userId = channelUserContext.getUserId(ctx.channel().id().asLongText());
            userChannelContext.remove(userId);
            channelUserContext.remove(ctx.channel().id().asLongText());
            System.out.println("长id : " + ctx.channel().id().asLongText() + " 客户端下线");
            System.out.println("短id : " + ctx.channel().id().asShortText() + " 客户端下线");

            if (log.getLevel().intLevel()== Level.DEBUG.intLevel()){
            log.debug("容器移除指定channel后剩余的链接");
            channelUserContext.outPut();
            userChannelContext.outPut();
        }

    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("长id : " + ctx.channel().id().asLongText() + " 客户端连接成功");
        System.out.println("短id : " + ctx.channel().id().asShortText() + " 客户端连接成功");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        //从自定义容器中移除
        String userId = channelUserContext.getUserId(ctx.channel().id().asLongText());
        userChannelContext.remove(userId);
        channelUserContext.remove(ctx.channel().id().asLongText());
        //发生异常之后关闭连接 之后从channelGroup中移除
        ctx.channel().close();
        //移除
        users.remove(ctx.channel());
    }

    /**
     * 通过userId获取channel 给客户端发送websocket消息
     *
     * @param channelId channelId
     * @param msg       消息
     * @return true发送成功 false发送失败不在线
     */
    public static boolean sendWebMsgByUserId(ChannelId channelId, DataContent msg) {
        Channel channel = users.find(channelId);
        //如果在线
        if (Objects.nonNull(channel)) {
            channel.writeAndFlush(new TextWebSocketFrame(JsonUtils.objectToJson(msg)));
            return true;
        }
        return false;
    }



}
